package io.github.luons.mediator.dsl.handler;

import io.github.luons.mediator.core.spi.result.ApiResult;
import io.github.luons.mediator.core.utils.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.UnexpectedTypeException;
import javax.validation.ValidationException;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * GlobalExceptionHandler
 *
 * @author : luons
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ServletRequestBindingException.class)
    public ApiResult<?> exceptionHandler(ServletRequestBindingException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
    }

    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ApiResult<?> exceptionHandler(HttpRequestMethodNotSupportedException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public ApiResult<?> exceptionHandler(ValidationException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, param is error "));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ApiResult<?> exceptionHandler(HttpMessageNotReadableException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, body is empty"));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(NestedRuntimeException.class)
    public ApiResult<?> exceptionHandler(NestedRuntimeException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, param nest exception"));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(UnexpectedTypeException.class)
    public ApiResult<?> exceptionHandler(UnexpectedTypeException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, unexpected exception"));
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    public ApiResult<?> runtimeExceptionHandler(RuntimeException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SQLException.class)
    public ApiResult<?> sqlExceptionExceptionHandler(SQLException e) {
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, sql exception"));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<?> validationBodyException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        if (!result.hasErrors() || CollectionUtils.isEmpty(result.getAllErrors())) {
            return ApiResult.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
        }
        List<String> errors = result.getAllErrors().stream()
                .filter(objectError -> objectError instanceof FieldError)
                .map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        log.error("【param check error】: errorMessage={}", JacksonUtils.toJson(errors));
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), "(" + String.join((";"), errors) + ")");
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public ApiResult<?> exceptionHandler(BindException e) {
        log.error(e.getMessage());
        return ApiResult.error(HttpStatus.BAD_REQUEST.value(), ("Bad Request, param bind exception"));
    }
}
